package cn.cloudtogo.staros.demo.droctopusspring.supplier.controller;

import cn.cloudtogo.staros.demo.droctopusspring.ErrorWebResult;
import cn.cloudtogo.staros.demo.droctopusspring.supplier.*;
import cn.cloudtogo.staros.demo.droctopusspring.workflowengine.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;

import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

@RestController
@RequestMapping
@RequiredArgsConstructor
@Slf4j
public class SupplierAuditController {


    public static final int UNCOMMITTED = 0;
    public static final int COMMITTED = 1;
    public static final int REJECTED = 2;
    public static final int APPROVED = 3;

    private final ObjectMapper objectMapper;

    private final WorkflowRepository workflowRepository;
    private final SupplierRepository supplierRepository;
    private final SupplierAuditRepository supplierAuditRepository;

    private final WebClient flowEnginWebClient;


    @PostMapping("/supplieraudits")
    public Mono<List<SupplierAudit>> audits(
            @RequestHeader("X-User-ID") String userId,
            @RequestHeader("X-WF-JWT") String wfJwt
    ) {
        return Mono.zip(
                        supplierAuditRepository.findAll().collectList()
                                .zipWith(supplierRepository.findAll().collectMap(SupplierModel::getId, Function.identity())),
                        wfApprovals(wfJwt)
                                .flatMap(page -> Flux.fromIterable(page.getRows())
                                        .flatMap(approval -> Mono.just(approval)
                                                .zipWith(wfGetApproval(wfJwt, approval.getId())))
                                        .collectList())
                )
                .flatMap(tuple2 -> {
                    Tuple2<List<SupplierAuditModel>, Map<Integer, SupplierModel>> tuple1 = tuple2.getT1();
                    List<SupplierAuditModel> supplierAuditModels = tuple1.getT1();
                    Map<Integer, SupplierModel> supplierModelMap = tuple1.getT2();

                    List<Tuple2<WorkflowEngineApproval, WorkflowEngineApprovalInfo>> wfApprovalInfos = tuple2.getT2();


                    List<SupplierAudit> audits = new LinkedList<>();
                    for (SupplierAuditModel supplierAuditModel : supplierAuditModels) {
                        SupplierModel supplierModel = supplierModelMap.get(supplierAuditModel.getSupplierId());

                        SupplierAudit supplierAudit = new SupplierAudit();
                        supplierAudit.setId(supplierAuditModel.getId());

                        supplierAudit.setSupplierId(supplierModel.getId());
                        supplierAudit.setSupplierName(supplierModel.getName());

                        supplierAudit.setAuditStatus(supplierAuditModel.getAuditStatus());
                        supplierAudit.setAuditTime(supplierAuditModel.getAuditTime());
                        supplierAudit.setAuditComments(supplierAuditModel.getAuditComments());

                        supplierAudit.setFormJson(supplierAuditModel.getFormJson());


                        String flowInstanceId = supplierAuditModel.getFlowInstanceId();
                        supplierAudit.setFlowInstanceId(flowInstanceId);

                        Tuple2<WorkflowEngineApproval, WorkflowEngineApprovalInfo> tuple = getApproval(wfApprovalInfos, flowInstanceId);
                        if (tuple != null) {
                            WorkflowEngineApproval approval = tuple.getT1();
                            supplierAudit.setFlowInstanceName(approval.getWorkFlowName());
                            supplierAudit.setCreatedBy(approval.getApplyName());

                            WorkflowEngineApprovalInfo info = tuple.getT2();
                            supplierAudit.setCanApprove(!info.getCurrentTaskList().isEmpty());
                        }

                        audits.add(supplierAudit);
                    }

                    return Mono.just(audits);
                });
    }

    private Tuple2<WorkflowEngineApproval, WorkflowEngineApprovalInfo> getApproval(List<Tuple2<WorkflowEngineApproval,
            WorkflowEngineApprovalInfo>> infos, String id) {
        for (Tuple2<WorkflowEngineApproval, WorkflowEngineApprovalInfo> tuple2 : infos) {
            WorkflowEngineApproval engineApproval = tuple2.getT1();
            if (engineApproval.getId().equals(id)) {
                return tuple2;
            }
        }
        return null;
    }


    @PostMapping("/suppliers/{supplierId}/audit")
    public Mono<ResponseEntity<SupplierAuditModel>> createAudit(
            @RequestHeader("X-WF-JWT") String wfJwt,
            @PathVariable Integer supplierId,
            @RequestBody Map<String, Object> form
    ) {
        return supplierRepository.findById(supplierId)
                .zipWith(jsonifyForm(form))
                .zipWith(workflowRepository.findByBoName("supplier")
                        .switchIfEmpty(Mono.error(new RuntimeException("找不到'supplier'对应的流程信息")))
                )
                .flatMap(tuple2 -> {
                    Tuple2<SupplierModel, String> t1 = tuple2.getT1();
                    SupplierModel supplierModel = t1.getT1();
                    String json = t1.getT2();

                    WorkflowModel workflowModel = tuple2.getT2();

                    String flowModelId = workflowModel.getFlowModelId();
                    return wfCreateAudit(wfJwt, flowModelId, json)
                            .doOnError(throwable -> ResponseEntity.badRequest().body(ErrorWebResult.of(throwable.getMessage())))
                            .flatMap(result -> saveToDB(supplierId, supplierModel, json, result))
                            .flatMap(result -> Mono.just(ResponseEntity.ok().body(result)));
                })
                .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
    }

    private Mono<String> jsonifyForm(Map<String, Object> form) {
        try {
            String json = objectMapper.writeValueAsString(form);
            return Mono.just(json);
        } catch (JsonProcessingException e) {
            return Mono.error(new RuntimeException(e));
        }
    }

    private Mono<WorkflowEngineResult<String>> wfCreateAudit(String wfJwt, String flowModelId, String json) {
        WorkflowCreateParams workflowCreateParams = new WorkflowCreateParams();
        workflowCreateParams.setFormJson(json);
        workflowCreateParams.setOptType("1");
        workflowCreateParams.setWorkflowId(flowModelId);

        return flowEnginWebClient.post()
                .uri(uriBuilder -> uriBuilder.path("/flow/api/save").queryParam("wf-auth-token", wfJwt).build())
                .bodyValue(workflowCreateParams)
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<WorkflowEngineResult<String>>() {
                })
                .flatMap(result -> {
                    if (result.isSuccess()) {
                        return Mono.just(result);
                    } else {
                        return Mono.error(new RuntimeException(result.getMsg()));
                    }
                });
    }

    private Mono<SupplierAuditModel> saveToDB(Integer supplierId, SupplierModel supplierModel, String json, WorkflowEngineResult<String> result) {
        SupplierAuditModel supplierAuditModel = new SupplierAuditModel();
        supplierAuditModel.setSupplierId(supplierId);
        supplierAuditModel.setFlowInstanceId(result.getObj());
        supplierAuditModel.setAuditTime(LocalDateTime.now());
        supplierAuditModel.setAuditStatus(COMMITTED);
        supplierAuditModel.setAuditComments(null);
        supplierAuditModel.setFormJson(json);

        return supplierAuditRepository.save(supplierAuditModel)
                .flatMap(savedSupplierAuditModel -> {
                    supplierModel.setAuditId(savedSupplierAuditModel.getId());
                    supplierModel.setAuditStatus(COMMITTED);
                    return supplierRepository.save(supplierModel)
                            .map(__ -> savedSupplierAuditModel);
                });
    }


    @PostMapping("/suppliers/{supplierId}/audit/approval")
    public Mono<ResponseEntity<Object>> approveAudit(
            @RequestHeader("X-User-ID") String userId,
            @RequestHeader("X-WF-JWT") String wfJwt,
            @PathVariable Integer supplierId,
            @RequestBody SupplierAuditApproveParams approveParams
    ) {
        return supplierRepository.findById(supplierId)
                .flatMap(supplierModel -> {
                    Integer auditStatus = supplierModel.getAuditStatus();
                    if (auditStatus == null || auditStatus != COMMITTED) {
                        return Mono.error(new RuntimeException("状态异常，无法进行审批"));
                    }

                    return supplierAuditRepository.findById(supplierModel.getAuditId())
                            .flatMap(supplierAuditModel -> approve(approveParams, supplierModel, supplierAuditModel, userId, wfJwt)
                                    .flatMap(__ -> Mono.just(ResponseEntity.ok().build()))
                                    .onErrorResume(throwable -> {
                                        log.warn("处理出错", throwable);
                                        return Mono.just(ResponseEntity.internalServerError().body(ErrorWebResult.of(throwable.getMessage())));
                                    })
                                    .switchIfEmpty(Mono.just(ResponseEntity.ok().build()))
                            )
                            .switchIfEmpty(Mono.just(ResponseEntity.internalServerError().body(ErrorWebResult.of("找不到审批记录"))));
                })
                .onErrorResume(throwable -> Mono.just(ResponseEntity.internalServerError().body(ErrorWebResult.of(throwable.getMessage()))))
                .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
    }

    private Mono<Tuple2<SupplierModel, SupplierAuditModel>> approve(SupplierAuditApproveParams approveParams,
                                                                    SupplierModel supplierModel, SupplierAuditModel supplierAuditModel,
                                                                    String userId, String wfJwt) {
        String flowInstanceId = supplierAuditModel.getFlowInstanceId();

        return wfApprove(approveParams, wfJwt, flowInstanceId, userId)
                .flatMap(wfInstance -> {
                    int auditStatus = COMMITTED;

                    String status = wfInstance.getStatus();
                    if ("5".equals(status)) {
                        auditStatus = REJECTED;
                    } else if ("3".equals(status)) {
                        auditStatus = APPROVED;
                    }


                    supplierModel.setAuditStatus(auditStatus);
                    supplierAuditModel.setAuditStatus(auditStatus);

                    return Mono.zip(
                            supplierRepository.save(supplierModel),
                            supplierAuditRepository.save(supplierAuditModel)
                    );
                });
    }

    private Mono<WorkflowEngineInstance> wfApprove(SupplierAuditApproveParams approveParams, String wfJwt,
                                                   String flowInstanceId, String userId) {
        return wfGetApproval(wfJwt, flowInstanceId)
                .flatMap(approvalInfo -> !approvalInfo.getCurrentTaskList().isEmpty()
                        ? Mono.just(approvalInfo.getCurrentTaskList().get(0))
                        : Mono.error(new RuntimeException("当前用户无权限审批")))
                .flatMap(wfTask -> wfApprove(flowInstanceId, wfTask.getId(), wfJwt, approveParams))
                .flatMap(__ -> wfGetFlowInstance(wfJwt, flowInstanceId));
    }


    private Mono<WorkflowEnginePage<WorkflowEngineApproval>> wfApprovals(String wfJwt) {
        return flowEnginWebClient.get()
                .uri(uriBuilder -> uriBuilder.path("/flow/api/approvalList").queryParam("wf-auth-token", wfJwt).build())
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<WorkflowEngineResult<WorkflowEnginePage<WorkflowEngineApproval>>>() {
                })
                .flatMap(result -> {
                    if (result.isSuccess()) {
                        return Mono.just(result.getObj());
                    } else {
                        return Mono.error(new RuntimeException(result.getMsg()));
                    }
                });
    }

    private Mono<WorkflowEngineApprovalInfo> wfGetApproval(String wfJwt, String id) {
        return flowEnginWebClient.get()
                .uri(uriBuilder -> uriBuilder.path("/flow/api/getApproveInfo")
                        .queryParam("wf-auth-token", wfJwt)
                        .queryParam("id", id).build())
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<WorkflowEngineResult<WorkflowEngineApprovalInfo>>() {
                })
                .flatMap(result -> {
                    if (result.isSuccess()) {
                        return Mono.just(result.getObj());
                    } else {
                        return Mono.error(new RuntimeException(result.getMsg()));
                    }
                });
    }

    private Mono<WorkflowEngineResult<Void>> wfApprove(String flowInstanceId, String taskId, String wfJwt,
                                                       SupplierAuditApproveParams approveParams) {
        boolean approve = approveParams.isApprove();


        WorkflowEngineApproveParams wfApproveParams = new WorkflowEngineApproveParams();
        wfApproveParams.setId(flowInstanceId);
        wfApproveParams.setTaskId(taskId);
        wfApproveParams.setComment(approveParams.getComment());

        if (approve) {
            wfApproveParams.setResult("Y");
        } else {
            wfApproveParams.setResult("N");
            wfApproveParams.setHisTaskId("-1");
        }

        return flowEnginWebClient.post()
                .uri(uriBuilder -> uriBuilder.path("/flow/api/doApprove").queryParam("wf-auth-token", wfJwt).build())
                .bodyValue(wfApproveParams)
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<WorkflowEngineResult<Void>>() {
                })
                .flatMap(result -> {
                    if (result.isSuccess()) {
                        return Mono.just(result);
                    } else {
                        return Mono.error(new RuntimeException(result.getMsg()));
                    }
                });
    }

    private Mono<WorkflowEngineInstance> wfGetFlowInstance(String wfJwt, String flowInstanceId) {
        return flowEnginWebClient.get()
                .uri(uriBuilder -> uriBuilder.path("/flow/api/instance")
                        .queryParam("wf-auth-token", wfJwt)
                        .queryParam("businessKey", flowInstanceId)
                        .build()
                )
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<WorkflowEngineResult<WorkflowEngineInstance>>() {
                })
                .flatMap(result -> {
                    if (result.isSuccess()) {
                        return Mono.just(result.getObj());
                    } else {
                        return Mono.error(new RuntimeException(result.getMsg()));
                    }
                });
    }

    private WorkflowEngineApprovalInfoTask getTask(List<WorkflowEngineApprovalInfoTask> currentTaskList, String userId) {
        for (WorkflowEngineApprovalInfoTask task : currentTaskList) {
            if (Objects.equals(task.getName(), userId)) {
                return task;
            }
        }
        return null;
    }
}
